using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.ComponentModel;
using System.Drawing;
using System.Web.UI.Design;
using System.Web.Security;
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    #region Documentation.
    /// <summary>
    /// Displays a custom designed button control on a Web page.
    /// </summary>
    /// <remarks><para>TemplateButton enables the design of custom button controls. You can specifiy the custom 
    /// HTML code, client side scripts (either embedded in the page or in extern files), CSS styles etc
    /// to achieve rich-style controls which respond to various mouse events.</para>
    /// <para>If no template is defined, the control is rendered as HTML <b>span</b> element with postback/submit functionalities.</para>
    /// </remarks>
    /// <example><para>The following example creates a Save button which displays the Text property followed by 
    /// AccessKey property in brackets. When rendered, the control looks like this: "Save (S)".</para>
    /// <code>
    /// TemplateButton btn = new TemplateButton();
    /// btn.Template = "{0} ({1})";
    /// btn.TemplateArguments = "Text;AccessKey"; 
    /// btn.Text = "Save";
    /// btn.AccessKey = "S";
    /// </code>
    /// </example>
    #endregion
    [DefaultProperty("Text")]
    [DefaultEvent("Click")]
    [ToolboxData("<{0}:TemplateButton runat=server></{0}:TemplateButton>")]
    public class TemplateButton : WebControl, IButtonControl, INamingContainer
    {
        #region FieldPermissions.

        Button btnSubmit = new Button();
        LiteralControl innerHtml = new LiteralControl();

        string text;
        string template;
        string[] templateArguments = new string[0];
        string clientScriptBlock;
        string[] clientScriptIncludeUrls = new string[0];

        string prependScript;
        string appendScript;
        bool confirmationRequired;
        string confirmationMessage;        

        #endregion

        #region CTor and CreateChildControls.

        /// <summary>
        /// Initializes a new instance of <b>TemplateButton</b> class.
        /// </summary>
        public TemplateButton()
        {
        }

        /// <summary>
        /// Called by the ASP.NET page framework to notify server controls that use composition-based 
        /// implementation to create any child controls they contain in preparation for posting back or rendering. 
        /// </summary>
        protected override void CreateChildControls()
        {
            this.Controls.Clear();
            this.Controls.Add(this.innerHtml);
            this.Controls.Add(this.btnSubmit);
            this.btnSubmit.Style["display"] = "none";
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets button's HTML template/format string.
        /// </summary>
        /// <value>A string containing zero or more format items compatibile with <see cref="String.Format(string, object[])"/> 
        /// method. If null or empty string, the button will be rendered as a <see cref="LinkButton"/> control.</value>
        /// <example>The following example creates a Save button which displays the Text property followed by AccessKey 
        /// property in brackets. When rendered, the control looks like this: "Save (S)".
        /// <code>
        /// TemplateButton btn = new TemplateButton();
        /// btn.Template = "{0} ({1})";
        /// btn.TemplateArguments = "Text;AccessKey"; 
        /// btn.Text = "Save";
        /// btn.AccessKey = "S";
        /// </code>
        /// </example>
        [DefaultValue(null)]
        [Description("HTML template/format string.")]
        [Category("Behavior")]
        public virtual string Template
        {
            get { return this.template; }
            set { this.template = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted TemplateButton property names whose values are formatted as defined by <see cref="Template"/>.
        /// </summary>
        /// <value>A string containg semicolon delimitted TemplateButton property names. The number of properties
        /// must match the number of format items defined by <see cref="Template"/>.</value>
        /// <example>The following example creates a Save button which displays the Text property followed by AccessKey 
        /// property in brackets. When rendered, the control looks like this: "Save (S)".
        /// <code>
        /// TemplateButton btn = new TemplateButton();
        /// btn.Template = "{0} ({1})";
        /// btn.TemplateArguments = "Text;AccessKey"; 
        /// btn.Text = "Save";
        /// btn.AccessKey = "S";
        /// </code>
        /// </example>
        [DefaultValue("")]
        [Description("Semicolon delimitted TemplateButton properties which are formatted as defined by Template.")]
        [Category("Behavior")]
        public virtual string TemplateArguments
        {
            get
            {
                string semicolDelimitedList = "";
                foreach (string arg in this.templateArguments)
                    semicolDelimitedList += arg + ";";

                if (semicolDelimitedList.Length > 0)
                    semicolDelimitedList = semicolDelimitedList.Substring(0, semicolDelimitedList.Length - 1);

                return semicolDelimitedList;
            }
            set
            {
                if (value == null)
                    value = "";

                this.templateArguments = value.Split(';');
            }
        }

        /// <summary>
        /// Gets or sets optional client side scripts which are used in <see cref="Template"/>.
        /// </summary>
        /// <value>A string containing all required scripts.</value>
        /// <remarks>TemplateButton control detects if same scripts are used by other TemplateButton controls.
        /// In that case, the scripts are rendered only once.</remarks>
        [DefaultValue(null)]
        [Description("Client side scripts which are used in Template.")]
        [Category("Behavior")]
        public virtual string ClientScriptBlock
        {
            get { return this.clientScriptBlock; }
            set { this.clientScriptBlock = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted JavaScript URLs/files which should be included in the page.
        /// </summary>
        /// <value>A string containing all JavaScript URLs/files required by <see cref="Template"/>.</value>
        /// <remarks>TemplateButton control detects if same URLs are used by other TemplateButton controls.
        /// In that case, the HTML include statements are registered only once.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted JavaScript URLs/files which should be included in the page.")]
        [Category("Behavior")]
        public string ClientScriptIncludeUrls
        {
            get
            {
                string semicolDelimitedList = "";
                foreach (string arg in this.clientScriptIncludeUrls)
                    semicolDelimitedList += arg + ";";

                if (semicolDelimitedList.Length > 0)
                    semicolDelimitedList = semicolDelimitedList.Substring(0, semicolDelimitedList.Length - 1);

                return semicolDelimitedList;
            }
            set
            {
                if (value == null)
                    value = "";

                this.clientScriptIncludeUrls = value.Split(new char[]{';'}, StringSplitOptions.RemoveEmptyEntries);
            }
        }

        /// <summary>
        /// Gets or sets the client-side script that is executed when a button is clicked.
        /// </summary>
        /// <value>JavaScript code. Default is <b>null</b>.</value>        
        [Category("Behavior")]
        [Description("Client-side script that is executed when a button is clicked.")]
        [DefaultValue("")]
        public virtual string OnClientClick
        {
            get { return this.Attributes["onclick"]; }
            set { this.Attributes["onclick"] = value; }
        }

        /// <summary>
        /// Gets or sets JavaScript block which is prepended to onclick script.
        /// </summary>
        /// <value>JavaScript code. Default is <b>null</b>.</value> 
        /// <remarks>A set of JavaScript statements or an incomplete JavaScript block.
        /// <code>if (confirm("Are you sure")) {</code>
        /// If an incomplete block is used, you have to specify <see cref="AppendScript"/>.
        /// </remarks>
        [Category("Behavior")]
        [Description("JavaScript block which is prepended to onclick script.")]
        [DefaultValue(null)]
        public virtual string PrependScript
        {
            get { return this.prependScript; }
            set { this.prependScript = value; }
        }

        /// <summary>
        /// Gets or sets JavaScript block which is appended to onclick script.
        /// </summary>
        /// <value>JavaScript code. Default is <b>null</b>.</value> 
        /// <remarks>A set of JavaScript statements or a code which completes the JavaScript block which is openned by <see cref="PrependScript"/>.
        /// Ver often only closing braces are used (<b>}</b>).</remarks>
        [Category("Behavior")]
        [Description("JavaScript block which is appended to onclick script.")]
        [DefaultValue(null)]
        public virtual string AppendScript
        {
            get { return this.appendScript; }
            set { this.appendScript = value; }
        }

        /// <summary>
        /// Gets or sets whether the control will popup a confirmation dialog.
        /// </summary>
        /// <value><b>true</b> to popup a confirmatio dialog; <b>false</b> otherwise. Default is <b>false</b>.</value>
        [Category("Behavior")]
        [Description("Indicates whether the control will popup a confirmation dialog.")]
        [DefaultValue(false)]
        public bool ConfirmationRequired
        {
            get { return this.confirmationRequired; }
            set { this.confirmationRequired = value; }
        }

        /// <summary>
        /// Gets or sets the message that is displayed in the confirmation dialog when the <see cref="ConfirmationRequired"/> property is set to <b>true</b>.
        /// </summary>
        /// <value>A text message. Default is <b>null</b>.</value>
        [Category("Behavior")]
        [Description("Message that is displayed in the confirmation dialog when the ConfirmationRequired property is set to true.")]
        [DefaultValue(null)]
        [Localizable(true)]
        public string ConfirmationMessage
        {
            get { return this.confirmationMessage; }
            set { this.confirmationMessage = value; }
        }

        #endregion

        #region PreRender. Register client-side resources.

        /// <summary>
        /// Raises the PreRender event.
        /// </summary>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (!string.IsNullOrEmpty(this.clientScriptBlock))
            {
                string key = "BizElements.Web.TemplateButton_" + FormsAuthentication.HashPasswordForStoringInConfigFile(this.clientScriptBlock, "MD5");
                if (!Page.ClientScript.IsClientScriptBlockRegistered(key))
                {
                    bool addScriptTags = !this.clientScriptBlock.Contains("<script");
                    Page.ClientScript.RegisterClientScriptBlock(Page.GetType(), key, this.clientScriptBlock, addScriptTags);
                }
            }

            if (this.clientScriptIncludeUrls != null)
            {
                foreach (string url in this.clientScriptIncludeUrls)
                {
                    string key = "BizElements.Web.TemplateButton_" + url;
                    if (!Page.ClientScript.IsClientScriptIncludeRegistered(url))
                        Page.ClientScript.RegisterClientScriptInclude(key, url);
                }
            }
        }

        #endregion

        #region Render.

        /// <summary>
        /// Renders the control to the specified HTML writter.
        /// </summary>
        protected override void Render(HtmlTextWriter writer)
        {
            if (!DesignMode)
            {
                // Dont' process clicks in design mode because "Generate Local Resource" tool emits the following ASPX fragment:
                // onclick="__doPostBack('','')" onclientclick="__doPostBack('','')"

                if (this.Enabled)
                {
                    string clientClick = this.Attributes["onclick"];
                    EnsureNotNull(ref clientClick);
                    EnsureEmptyOrEndsWithSemicol(ref clientClick);
                    clientClick += this.Page.ClientScript.GetPostBackEventReference(this.btnSubmit, null);
                    if (!string.IsNullOrEmpty(this.PrependScript))
                        clientClick = this.PrependScript + " " + clientClick;
                    if (!string.IsNullOrEmpty(this.AppendScript))
                        clientClick += " " + this.AppendScript;
                    if (this.ConfirmationRequired && !string.IsNullOrEmpty(this.ConfirmationMessage))
                    {
                        clientClick = "if (window.confirm('" + JScript.CleanString(this.ConfirmationMessage) + "')){ " + clientClick + "} ";
                    }

                    this.Attributes["onclick"] = clientClick;
                }
                else
                {
                    this.Attributes["onclick"] = "";
                }
            }

            if (string.IsNullOrEmpty(this.template))
            {
                this.innerHtml.Text = this.Text;
            }
            else
            {
                try
                {
                    this.innerHtml.Text = string.Format(this.template, GetTemplateArgValues());
                }
                catch (Exception e)
                {
                    if (this.DesignMode)
                        this.innerHtml.Text = e.Message;
                    else
                        throw;
                }
            }

            if (this.DesignMode)
                writer.Write(this.innerHtml.Text);
            else
                base.Render(writer);
        }

        private void EnsureNotNull(ref string script)
        {
            if (script == null)
                script = "";
        }

        private void EnsureEmptyOrEndsWithSemicol(ref string script)
        {
            script = script.Trim();
            if (script.Length > 0)
            {
                if (!script.EndsWith(";"))
                    script += ";";
            }
        }

        private string[] GetTemplateArgValues()
        {
            if (this.templateArguments == null)
                return new string[0];

            string[] argValues = new string[this.templateArguments.Length];
            for (int idxArg = 0; idxArg < argValues.Length; idxArg++)
                argValues[idxArg] = Convert.ToString(DataBinder.Eval(this, this.templateArguments[idxArg]));

            return argValues;
        }

        #endregion

        #region IButtonControl Members.

        /// <summary>
        /// Gets or sets a value indicating whether validation is performed when the Button control is clicked.
        /// </summary>
        /// <value>true if validation is performed when the Button control is clicked; otherwise, false. The default value is true. </value>
        [Category("Behavior")]
        [DefaultValue(true)]
        [Description("Whether the button causes validation to fire.")]
        public virtual bool CausesValidation
        {
            get { return this.btnSubmit.CausesValidation; }
            set { this.btnSubmit.CausesValidation = value; }
        }

        /// <summary>
        /// Occurs when the Button control is clicked. 
        /// </summary>
        /// <remarks>The Click event is raised when the Button control is clicked. This event is commonly used when no command name is associated with the Button control (for instance, with a Submit button).</remarks>
        [Category("Action")]
        [Description("Fires when the button is clicked.")]
        public event EventHandler Click
        {
            add { this.btnSubmit.Click += value; }
            remove { this.btnSubmit.Click -= value; }
        }

        /// <summary>
        /// Occurs when the Button control is clicked. 
        /// </summary>
        /// <remarks>The Command event is raised when the Button control is clicked. This event is commonly used when a command name, such as Sort, is associated with the Button control. This allows you to create multiple Button controls on a Web page and programmatically determine which Button control is clicked.</remarks>
        [Category("Action")]
        [Description("Fires when the button is clicked and an associated command is defined.")]
        public event CommandEventHandler Command
        {
            add { this.btnSubmit.Command += value; }
            remove { this.btnSubmit.Command -= value; }
        }

        /// <summary>
        /// Gets or sets an optional parameter passed to the <see cref="Command"/> event along with the associated <see cref="CommandName"/>. 
        /// </summary>
        /// <value>An optional parameter passed to the Command event along with the associated CommandName. The default value is String.Empty.</value>
        /// <remarks>Use the CommandArgument property to specify a parameter that compliments the CommandName property.</remarks>
        [Category("Behavior")]
        [Description("The command argument associated with the button.")]
        [DefaultValue("")]
        public virtual string CommandArgument
        {
            get { return this.btnSubmit.CommandArgument; }
            set { this.btnSubmit.CommandArgument = value; }
        }

        /// <summary>
        /// Gets or sets the command name associated with the Button control that is passed to the <see cref="Command"/> event. 
        /// </summary>
        /// <value>The command name of the Button control. The default value is String.Empty.</value>
        /// <remarks>When you have multiple Button controls on a Web page, use the CommandName property to specify or determine the command name associated with each Button control. You can set the CommandName property with any string that identifies the command to perform. You can then programmatically determine the command name of the Button control and perform the appropriate actions. Additional information about the command, such as Ascending, may be specified by using the CommandArgument property.</remarks>
        [Category("Behavior")]
        [Description("The command associated with the button.")]
        [DefaultValue("")]
        public virtual string CommandName
        {
            get { return this.btnSubmit.CommandName; }
            set { this.btnSubmit.CommandName = value; }
        }

        /// <summary>
        /// Gets or sets the URL of the page to post to from the current page when the Button control is clicked. 
        /// </summary>
        /// <value>The URL of the Web page to post to from the current page when the Button control is clicked. The default value is an empty string (""), which causes the page to post back to itself.</value>
        /// <remarks>The PostBackUrl property allows you to perform a cross-page post using the Button control. Set the PostBackUrl property to the URL of the Web page to post to when the Button control is clicked. For example, specifying Page2.aspx causes the page that contains the Button control to post to Page2.aspx. If you do not specify a value for the PostBackUrl property, the page posts back to itself.</remarks>
        [Category("Behavior")]
        [Description("The URL to post to when the button is clicked.")]
        [DefaultValue("")]
        public virtual string PostBackUrl
        {
            get { return this.btnSubmit.PostBackUrl; }
            set { this.btnSubmit.PostBackUrl = value; }
        }

        /// <summary>
        /// Gets or sets the text caption displayed for the button.
        /// </summary>
        /// <value>The text caption displayed for the button.</value>
        [Category("Appearance")]
        [Description("The text to be shown on the button.")]
        [DefaultValue("")]
        [Localizable(true)]
        public virtual string Text
        {
            get { return this.text ?? ""; }
            set { this.text = value; }
        }

        /// <summary>
        /// Gets or sets the group of controls for which the Button control causes validation when it posts back to the server.
        /// </summary>
        /// <value>The group of controls for which the Button control causes validation when it posts back to the server. The default value is an empty string ("").</value>
        /// <remarks>Validation groups allow you to assign validation controls on a page to a specific category. Each validation group can be validated independently of other validation groups on the page. Use the ValidationGroup property to specify the name of the validation group for which the Button control causes validation when it posts back to the server. This property has an effect only when the value of the CausesValidation property is set to true. When you specify a value for the ValidationGroup property, only the validation controls that are part of the specified group are validated when the Button control posts back to the server. If you do not specify a value for this property and the CausesValidation property is set to true, all validation controls on the page that are not assigned to a validation group are validated when the control posts back to the server.</remarks>
        [Category("Behavior")]
        [Description("The group that should be validated when the control causes a postback.")]
        [DefaultValue("")]
        public virtual string ValidationGroup
        {
            get { return this.btnSubmit.ValidationGroup; }
            set { this.btnSubmit.ValidationGroup = value; }
        }

        #endregion
    }
}
